/*
 * Copyright (c) 2009, Hamish I A Morgan
 * All rights reserved.
 */
package locusts.lib;

import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.logging.Logger;

/**
 * The <code>FrameCounter</code> class maintaining a counter and calculating the
 * FPS (Frames Per Second). Once instantiated the <code>markFrame()</code>
 * method should be called every time a time step occurs. The frame rate and
 * counter can then be read by calling <code>getFps()</code> and
 * <code>getFrameCount()</code> respectively.
 * <p>
 * It also writes to the default logger every time the FPS value is updated, at
 * log level <code>Level.INFO</code>.
 * </p>
 * <h3>Change Log:</h3>
 * <dl>
 * <dt><strong>Version 1.01:</strong></dt>
 * <dd>
 * <ul>
 * <li>Added logger output</li>
 * <li>Improved JavaDoc comments.</li>
 * </ul>
 * </dd>
 * </dd>
 * </dl>
 * 
 * @author Hamish I A Morgan <code>&lt;hiam20@sussex.ac.uk&gt;</code>
 * @version 1.01, 13<sup>th</sup> June 2009
 */
public final class FrameCounter {

    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(
            FrameCounter.class.getName());
    /**
     * Default time between FPS updates.
     */
    private static final long UPDATE_TIME_DEFAULT = 1000l;
    /**
     * Default name to use when printing with <code>toString()</code>.
     */
    private static final String DEFAULT_NAME = "Unnamed";
    /**
     * The <code>NumberFormat</code> object enables the FPS printing with only a
     * few decimal places. (Otherwise it is unreadable.)
     */
    private static final NumberFormat NUMBER_FORMAT = NumberFormat.
            getNumberInstance();
    /**
     * To get the <code>NumberFormat</code> object to write to a pre-existing
     * <code>StringBuffer</code> you have to pass in this pointless parameter.
     * Otherwise it will create a new <code>StringBuffer</code> every time.
     */
    private static final FieldPosition FIELD_POSITION = new FieldPosition(
            NumberFormat.INTEGER_FIELD);
    /**
     * Name to output when <code>toString</code> is called.
     */
    private final String name;
    /**
     * Time (in milliseconds) between updates to the FPS value.
     */
    private final long updateTime;
    /**
     * Total number of calls to markFrame since last reset.
     */
    private int count;
    /**
     * Counter value when the FPS value was last updated.
     */
    private int lastUpdateCount;
    /**
     * Time (in milliseconds) when FPS value was last updated.
     */
    private long lastUpdateTime;
    /**
     * The last calculated value for the frames-per-second.
     */
    private double fps;

    /**
     * Construct a new instance of the <code>FrameCounter</code> class that will
     * use the given name when <code>toString</code> is called and will update
     * the FPS values every updateTime milliseconds.
     * 
     * @param name
     *            What the counter is called for toString
     * @param updateTime
     *            How often the FPS value is updated (in milliseconds)
     * @throws IllegalArgumentException
     *             When the name parameter is null
     * @throws IllegalArgumentException
     *             When the updateTime is less than 1.
     */
    public FrameCounter(final String name, final long updateTime) {
        if (name == null)
            throw new IllegalArgumentException(
                    "The name parameter must be non-null");
        if (updateTime < 1)
            throw new IllegalArgumentException(
                    "The updateTime paramter must be creater than 0.");
        this.name = name;
        this.updateTime = updateTime;
        reset();
    }

    /**
     * Construct a new instance of the <code>FrameCounter</code> class. It will
     * be named <code>"Unnamed"</code> and will update the FPS counter ever
     * second (1000 milliseconds)
     * 
     * @throws IllegalArgumentException
     *             When the name parameter is null
     * @throws IllegalArgumentException
     *             When the updateTime is less than 1.
     */
    public FrameCounter() {
        this(DEFAULT_NAME, UPDATE_TIME_DEFAULT);
    }

    /**
     * Construct a new instance of the class that will use the given name when
     * toString is called. It will update the fps counter ever second (1000 ms).
     * 
     * @param name
     *            What the counter is called for toString
     * @throws IllegalArgumentException
     *             When the name parameter is null
     * @throws IllegalArgumentException
     *             When the updateTime is less than 1.
     */
    public FrameCounter(final String name) {
        this(name, UPDATE_TIME_DEFAULT);
    }

    /**
     * Method that sets all object fields to their default initial states. This
     * returns the <code>FrameCounter</code> object to the state it was in when
     * it was instantiated.
     */
    public void reset() {
        count = 0;
        lastUpdateCount = 0;
        lastUpdateTime = System.currentTimeMillis();
        fps = Double.POSITIVE_INFINITY;
    }

    /**
     * Record that a discrete time step has occurred and increment the frame
     * counter by 1.
     */
    public void markFrame() {
        count++;
    }

    /**
     * Update the FPS value if enough time has passed since the last update,
     * otherwise do nothing. In this way the FPS is not calculated all the time
     * and represents the average FPS over a period, defined by the
     * <code>updateTime</code> parameter to the constructor.
     */
    private void doUpdate() {
        final long time = System.currentTimeMillis();
        if (time - lastUpdateTime >= updateTime) {
            fps = 1000d * (count - lastUpdateCount) / (time -
                    lastUpdateTime);
            lastUpdateCount = count;
            lastUpdateTime = time;
            logger.info(toString());
        }
    }

    /**
     * Return the previously calculated FPS value. If not enough time has passed
     * since the object was reset (or constructed) then this will have the value
     * of <code>Double.POSITIVE_INFINITY</code>
     * 
     * @return number of frames per second recorded in the last interval.
     */
    public double getFps() {
        doUpdate();
        return fps;
    }

    /**
     * Return the total number of frames since the last reset or object
     * construction. This is the number of calls to the <code>markFrame()</code>
     * method.
     * 
     * @return number of frames since last reset.
     */
    public int getFrameCount() {
        return count;
    }

    /**
     * Return a string representation of the <code>FrameCounter</code> object.
     * It will consist of the number of the counter, as supplied in the
     * constructor, and last calculated frame rate in FPS (Frames Per Second.)
     * The FPS will be displayed using the default number formatter for the
     * default locale. It will probably look something like this:
     * 
     * <pre>
     * 		&lt;Name&gt; Frame Rate: 60.123 FPS
     * </pre>
     * 
     * @return string representation of the <code>FrameCounter</code> object.
     */
    @Override
    public String toString() {
        final StringBuffer buffer = new StringBuffer(32);
        if (name.length() > 0) {
            buffer.append(name);
            buffer.append(' ');
        }
        buffer.append("Frame Rate: ");
        if (getFps() == Double.POSITIVE_INFINITY)
            buffer.append("Calculating");
        else {
            NUMBER_FORMAT.format(getFps(), buffer, FIELD_POSITION);
            buffer.append(" FPS");
        }
        return buffer.toString();
    }
}
